package cn.demoncat.util.exec;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.io.CloseUtil;
import cn.demoncat.util.io.entity.LineErrorOutputStream;
import cn.demoncat.util.io.entity.LineOutputStream;
import cn.demoncat.util.lang.JsonUtil;
import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.constant.CharsetConstant;
import cn.demoncat.util.lang.constant.StatusConstant.Common;
import cn.demoncat.util.lang.constant.SystemConstant;
import cn.demoncat.util.lang.entity.LineReadCallback;
import cn.demoncat.util.lang.entity.LineReadCallback.LineReadFinishException;
import cn.demoncat.util.lang.entity.ListStr;
import cn.demoncat.util.lang.entity.TypeList;
import org.apache.commons.exec.*;
import org.apache.commons.lang3.ArrayUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

/**
 * 执行系统命令 - commons-exec
 * 
 * @author 延晓磊
 *
 * @since 2019年6月13日
 */
public class ExecUtil {

	// 系统命令
	public static final String BASH = "/bin/bash";
	public static final String BASHC = "-c";
	public static final String CMD = "cmd.exe";
	public static final String CMDC = "/c";
	/**
	 * 输出结果编码（测试时临时修改）
 	 */
	public static String CHARSET = CharsetConstant.UTF8;

	/**
	 * 执行系统命令
	 * 
	 * @param command	命令：第1个为命令，后面的是参数（空格分隔为多个参数）
	 * 
	 * @return 输出内容{1:[输出流内容], 0:[错误流内容]}。注意，输出类型不能作为执行状态判断标准
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static TypeList<String> exec(String... command) {
		// 命令
		CommandLine commandLine = getCommand(command);
		// 执行器
		DefaultExecutor executor = new DefaultExecutor();
		// 不检查执行状态码（可以设置指定成功码，然后返回不同的结果时抛异常）
		executor.setExitValues(null);
		// 输出流
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		ByteArrayOutputStream err = new ByteArrayOutputStream();
		PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(out, err);
		executor.setStreamHandler(pumpStreamHandler);
		// 阻塞执行
		try {
			executor.execute(commandLine);
		} catch (LineReadFinishException e) {
			// 停止读取
		} catch (Exception e) {
			throw new IoRuntimeException("执行系统命令失败：command = " + JsonUtil.toStr(command), e);
		}
		// 读取响应
		try {
			if (out.size() > 0) {
				return new TypeList<>(Common.YES, ListStr.getInstance(StringUtil.splitLine(out.toString(CHARSET))));
			} else {
				return new TypeList<>(Common.NO, ListStr.getInstance(StringUtil.splitLine(err.toString(CHARSET))));
			}
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}

	/**
	 * 执行系统命令 - 守护线程异步执行
	 * 
	 * @param command	命令：第1个为命令，后面的是参数（空格分隔为多个参数）
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static void asyncExec(String... command) {
		// 命令
		CommandLine commandLine = getCommand(command);
		// 执行器
		DaemonExecutor executor = new DaemonExecutor();
		// 不检查执行状态码（可以设置指定成功码，然后返回不同的结果时抛异常）
		executor.setExitValues(null);
		// 异步执行
		try {
			executor.execute(commandLine, new DefaultExecuteResultHandler());
		} catch (IOException e) {
			throw new IoRuntimeException("执行系统命令失败：command = " + JsonUtil.toStr(command), e);
		}
	}
	
	/**
	 * 执行系统命令
	 * 
	 * @param out		命令结果输出
	 * @param err		命令错误结果输出
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	命令
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static void exec(OutputStream out, OutputStream err, Long timeout, String... command) {
		// 命令
		CommandLine commandLine = getCommand(command);
		// 执行器
		DefaultExecutor executor = new DefaultExecutor();
		// 不检查执行状态码（可以设置指定成功码，然后返回不同的结果时抛异常）
		executor.setExitValues(null);
		// 输出流
		PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(out, err);
		executor.setStreamHandler(pumpStreamHandler);
		// 超时设置
		if (timeout != null && timeout > 0) {
			// 监控狗
			ExecuteWatchdog executeWatchdog = new ExecuteWatchdog(timeout);
			executor.setWatchdog(executeWatchdog);
		}
		// 阻塞执行
		try {
			executor.execute(commandLine);
		} catch (LineReadFinishException e) {
			// 停止读取
		} catch (Exception e) {
			throw new IoRuntimeException("执行系统命令失败：command = " + JsonUtil.toStr(command), e);
		} finally {
			CloseUtil.close(out);
			CloseUtil.close(err);
		}
	}

	/**
	 * 执行系统命令
	 * 
	 * @param callback	响应读取器
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	命令
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static void exec(LineReadCallback callback, Long timeout, String... command) {
		exec(new LineOutputStream(callback), new LineErrorOutputStream(callback), timeout, command);
	}

	/**
	 * 执行shell或cmd命令
	 *
	 * @param command
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月22日
	 */
	public static TypeList<String> osExec(String... command){
		if (SystemConstant.OS_IS_LINUX) {
			return shell(command);
		}else{
			return cmd(command);
		}
	}

	/**
	 * 执行shell或cmd命令 - 守护线程异步执行
	 *
	 * @param command
	 *
	 * @author 延晓磊
	 * @since 2020年10月22日
	 */
	public static void osAsyncExec(String... command){
		if (SystemConstant.OS_IS_LINUX) {
			asyncShell(command);
		}else{
			asyncCmd(command);
		}
	}

	/**
	 * 执行shell或cmd命令
	 *
	 * @param out		命令结果输出
	 * @param err		命令错误结果输出
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	shell文件/多个命令
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static void osExec(OutputStream out, OutputStream err, Long timeout, String... command) {
		if (SystemConstant.OS_IS_LINUX) {
			shell(out, err, timeout, command);
		}else{
			cmd(out, err, timeout, command);
		}
	}

	/**
	 * 执行shell或cmd命令
	 *
	 * @param callback	响应读取器
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	shell文件/多个命令
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static void osExec(LineReadCallback callback, Long timeout, String... command) {
		if (SystemConstant.OS_IS_LINUX) {
			shell(callback, timeout, command);
		}else{
			cmd(callback, timeout, command);
		}
	}

	/**
	 * 执行shell脚本、命令
	 * 
	 * @param command	shell文件/多个命令
	 * 
	 * @return 输出内容{1:[输出流内容], 0:[错误流内容]}。注意，输出类型不能作为执行状态判断标准
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static TypeList<String> shell(String... command) {
		return exec(BASH, BASHC, moreCommand(command));
	}

	/**
	 * 执行shell脚本、命令 - 守护线程异步执行
	 * 
	 * @param command	shell文件/多个命令
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static void asyncShell(String... command) {
		asyncExec(BASH, BASHC, moreCommand(command));
	}
	
	/**
	 * 执行shell脚本、命令
	 * 
	 * @param out		命令结果输出
	 * @param err		命令错误结果输出
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	shell文件/多个命令
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static void shell(OutputStream out, OutputStream err, Long timeout, String... command) {
		exec(out, err, timeout, BASH, BASHC, moreCommand(command));
	}

	/**
	 * 执行shell脚本、命令
	 * 
	 * @param callback	响应读取器
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	shell文件/多个命令
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static void shell(LineReadCallback callback, Long timeout, String... command) {
		exec(callback, timeout, BASH, BASHC, moreCommand(command));
	}

	/**
	 * 执行cmd命令
	 * 
	 * @param command	多个命令
	 * 
	 * @return 输出内容{1:[输出流内容], 0:[错误流内容]}。注意，输出类型不能作为执行状态判断标准
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static TypeList<String> cmd(String... command) {
		return exec(CMD, CMDC, moreCommand(command));
	}

	/**
	 * 执行cmd脚本、命令 - 守护线程异步执行
	 * 
	 * @param command	多个命令
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static void asyncCmd(String... command) {
		asyncExec(CMD, CMDC, moreCommand(command));
	}
	
	/**
	 * 执行cmd命令
	 * 
	 * @param out		命令结果输出
	 * @param err		命令错误结果输出
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	多个命令
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static void cmd(OutputStream out, OutputStream err, Long timeout, String... command) {
		exec(out, err, timeout, CMD, CMDC, moreCommand(command));
	}

	/**
	 * 执行cmd命令
	 * 
	 * @param callback	响应读取器
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	多个命令
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static void cmd(LineReadCallback callback, Long timeout, String... command) {
		exec(callback, timeout, CMD, CMDC, moreCommand(command));
	}

	/**
	 * 创建命令行
	 * 
	 * @param command	命令：第1个为命令，后面的是参数（空格分隔为多个参数）
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	private static CommandLine getCommand(String[] command) {
		// 创建命令行
		CommandLine commandLine = CommandLine.parse(command[0]);
		// 命令参数
		if (command.length > 1) {
			commandLine.addArguments(ArrayUtils.subarray(command, 1, command.length), false);
		}
		return commandLine;
	}

	/**
	 * 合并多个命令，使用&&拼接
	 * 
	 * @param command
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月17日
	 */
	public static String moreCommand(String[] command) {
		if (command.length == 1) {
			return command[0];
		} else {
			StringBuilder str = new StringBuilder(command[0]);
			for (int i = 1; i < command.length; i++) {
				str.append(" && ").append(command[i]);
			}
			return str.toString();
		}
	}

}
